17. Exercise: Synchronization

Exercise: Synchronization

Parallel Test Runner

Remember that unit testing framework you designed in the previous lesson? You have a test suite that has been taking too long to finish.

You have an idea to speed it up: What if the individual unit tests could run in parallel, using multiple threads? For example, suppose you have four unit test methods that each take a few seconds to complete. When running these tests normally, the test runner can take 10+ seconds to complete. On the other hand, if you could make the test runner execute the tests in parallel, you'd expect it to take only a few seconds total to complete, since all the test methods run concurrently with one another.

Your starter code for this exercise is the solution to the Custom ClassLoader exercise from the previous lesson. If you want, you can run the tests now — they should take 10+ seconds to complete.

javac *.java */*.java
java -ea TestRunner tests/ CalculatorTest

Note: If you peek in CalculatorTest.java, you'll notice the tests aren't really doing a whole lot; they use Thread#sleep() to make them artificially long-running for the purposes of this exercise.

Here's what you need to do to enhance the test runner to run tests in parallel:

  1. Create an ExecutorService using Executors.newFixedThreadPool(...). This is the thread pool that will run all the tests.
  2. There should already be a loop that decide which test methods to run. You should update the contents of the loop to run inside of a Runnable. You don't actually have to create a separate class for this — the easiest way is to use a lambda:
for (...) {
  executor.execute(() -> {
    // TODO: Run the next test.
  });
}
  1. Next, you need to synchronize access to the passed and failed lists. There are a variety of ways to do this. You could use the synchronized keyword, an explicit ReentrantLock, or even a CopyOnWriteArrayList if you are feeling adventurous (though CopyOnWriteArrayList is probably overkill here).
  2. Finally, outside the loop, you need to wait for all the test threads to finish (this is also called joining the threads. There are multiple ways to do this. One way is to use a CountDownLatch initialized with the number of test methods. Then each test thread should call CountDownLatch#countDown() when it's done. Then, outside the loop, you can call CountDownLatch#await(). This method waits until the CountDownLatch counts down to zero, which means all the threads have called the countDown() method. When you are done joining the test threads in this way, make sure you call ExecutorService#shutdown(), or else the program might not actually exit.

Running the solution

Try out the parallel test runner!

javac *.java */*.java
java -ea TestRunner tests/ CalculatorTest

Do the tests complete noticeably faster than before?

TODO List

Task List:

Task Feedback:

Congratulations, you built a multi-threaded parallel test runner!

In addition to running test suites faster, running tests in parallel can be useful to uncover subtle concurrency bugs in the systems being tested (and sometimes bugs in the test fixtures themselves).

Code

If you need a code on the https://github.com/udacity.

  • userCode:

    export PATH=/data/jdk-15.0.1/bin:$PATH
    export JAVA_HOME=/data/jdk-15.0.1/bin